package trace_log

import (
	"bytes"
	"fmt"
	logger4 "github.com/yougg/log4go"
	"os"
	"path/filepath"
	"runtime"
	"runtime/debug"
	"strings"
	"sync"
	"time"
)

type TLog struct {
	log      *TFileLog
	level    int
	TimeUnix int64
}

func NewLog(log *TFileLog, level int) *TLog {
	return &TLog{
		log:      log,
		level:    level,
		TimeUnix: time.Now().Unix(),
	}
}

func (t *TLog) Error(args ...interface{}) {
	defer ExceptionCatch()

	if t.log != nil {
		t.log.Error(args)
		t.print(format(args...))
	}
}

func (t *TLog) Warn(args ...interface{}) {
	defer ExceptionCatch()

	if t.log != nil {
		t.log.Warn(args)
		t.print(format(args...))
	}
}

func (t *TLog) Info(args ...interface{}) {
	defer ExceptionCatch()

	if t.log != nil {
		t.log.Info(args)
		t.print(format(args...))
	}
}

func (t *TLog) Debug(args ...interface{}) {
	defer ExceptionCatch()

	if t.log != nil {
		t.log.Debug(args)
		t.print(format(args...))
	}
}

func (t *TLog) print(arg0 interface{}, args ...interface{}) {
	switch first := arg0.(type) {
	case string:
		// Use the string as a format string
		t.intLogf(first, args...)
	default:
		// Build a format string so that it will be similar to Sprint
		t.intLogf(fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
	}
}

func (t *TLog) intLogf(format string, args ...interface{}) {
	// Determine caller func
	pc, _, lineno, ok := runtime.Caller(3)
	src := ""
	if ok {
		src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno)
	}

	msg := format
	if len(args) > 0 {
		msg = fmt.Sprintf(format, args...)
	}
	goID := fmt.Sprintf("GoID:%05s", goID())
	szTime := time.Unix(time.Now().Unix(), 0).Format("2006-01-02 03:04:05")
	fmt.Println("[", szTime, "][", goID, "][", src, "]", msg)
	return
}

func GOID() string {
	return goID()
}

// Get go routine ID from runtime stack
func goID() (id string) {
	routinePrefix := []byte("goroutine ")
	var buf [64]byte
	n := runtime.Stack(buf[:], false)
	id = string(bytes.Fields(bytes.TrimPrefix(buf[:n], routinePrefix))[0])
	return
}

func format(args ...interface{}) (msg string) {
	msg = fmt.Sprintf(strings.Repeat(" %v", len(args)), args...)
	return
}

var (
	mutex sync.Mutex
)

type TFileLog struct {
	log logger4.Logger
}

func NewFileLog(logPath string, logName string, funcCallDepth int) (*TFileLog, error) {
	defer ExceptionCatch()
	mutex.Lock()
	defer mutex.Unlock()

	exist, err := pathExists(logPath)
	if err != nil {
		fmt.Println("[EROR] failed to path exist,err:", err)
		return nil, err
	}

	//如果文件夹不存在，则要新创建文件夹
	if !exist {
		err = os.MkdirAll(logPath, 0777)
		if err != nil {
			fmt.Println("[EROR] failed to new log dir,err:", err)
			return nil, err
		}
	}

	logName = filepath.Join(logPath, logName)

	logWriter := logger4.NewFileLogWriter(logName, false)
	logWriter.SetFormat("[%T] [%L] (%I) (%s) %M ")
	logWriter.SetRotate(true)
	logWriter.SetRotateSize(50 * 1024 * 1024)
	logWriter.SetRotateDaily(true)
	logWriter.SetMaxDays(10)

	logger4.EnableGoRoutineID = true
	logger4.FuncCallDepth = 3 + funcCallDepth

	v := logger4.Logger{}
	v.AddFilter("file", logger4.DEBUG, logWriter)

	return &TFileLog{log: v}, nil
}

// 日志级别 FINE DEBUG TRACE INFO WARNING ERROR CRITICAL （1--7）
func (p *TFileLog) SetLogLevel(level int) {
	p.log["file"].Level = logger4.Level(level)
}

func (p *TFileLog) Debug(args ...interface{}) {
	p.log.Debug(format(args...))
}

func (p *TFileLog) Info(args ...interface{}) {
	p.log.Info(format(args...))
}

func (p *TFileLog) Warn(args ...interface{}) {
	p.log.Warn(format(args...))
}

func (p *TFileLog) Error(args ...interface{}) {
	p.log.Error(format(args...))
}

func (p *TFileLog) Critical(args ...interface{}) {
	p.log.Critical(format(args...))
}

func pathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func ExceptionCatch() {
	if err := recover(); err != nil {
		fmt.Println("err:\n", err, "Exception: \n", string(debug.Stack()))
	}
}
